﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

using SvnCore;

using SVNTreeViewer.Views;
using SVNTreeViewer.VisualTreeBuilder;

using Path = System.Windows.Shapes.Path;

namespace SVNTreeViewer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
            this.RevisionsAdded = new List<RevisionBlock>();
            this.ConnectionsAdded = new List<RevisionConnection>();
        }

        protected List<RevisionConnection> ConnectionsAdded { get; set; }

        /// <summary>
        /// Gets or sets the revisions added.
        /// </summary>
        /// <value>
        /// The revisions added.
        /// </value>
        protected List<RevisionBlock> RevisionsAdded { get; set; }

        private PathPainter PathPainter { get; set; }

        /// <summary>
        /// Adds the index to revisions.
        /// </summary>
        /// <param name="branches">The branches.</param>
        /// <returns>Adds index to revisions, based on their place in a ordered revison number list.</returns>
        private IEnumerable<Revision> AddIndexToRevisions(IEnumerable<Branch> branches)
        {
            IOrderedEnumerable<Revision> revisions = branches.SelectMany(x => x.Revisions).OrderBy(revision => revision.Number);

            long index = 0;
            long previousRevisionNumber = -1;
            foreach (Revision revision in revisions)
            {
                if (revision.Number != previousRevisionNumber)
                {
                    index++;
                    previousRevisionNumber = revision.Number;
                }

                revision.Index = index;
            }

            SvnFacade svnFacade = new SvnFacade();

            svnFacade.FillMergeInfo(branches);

            return revisions;
        }

        /// <summary>
        /// News the SVN tree.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void NewSvnTree(object sender, RoutedEventArgs e)
        {
            SvnDirectories dir = new SvnDirectories();
            bool? dialogResult = dir.ShowDialog();

            if (dialogResult.HasValue)
            {
                this.UpdateLayout();

                if (dialogResult.Value)
                {
                    TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
                    this.ClearTree();
                    if (dir.SvnDir.Count > 0)
                    {
                        this.Loader.Visibility = Visibility.Visible;
                        this.Loader.AddInfo("Loading...");
                        Task<IEnumerable<Branch>> branches = new Task<IEnumerable<Branch>>(() => this.GetBranches(dir));
                        Task<IEnumerable<Revision>> revisions = branches.ContinueWith(task => this.AddIndexToRevisions(task.Result));

                        revisions.ContinueWith(
                            task =>
                                {
                                    int level = 0;
                                    foreach (Branch branch in branches.Result.OrderBy(x => x.Uri.ToString()))
                                    {
                                        Grid branchGrid = new BranchContainer(branch).CreateBranch();
                                        this.BranchesStack.Children.Add(branchGrid);
                                        level++;
                                    }

                                    foreach (Revision revision in revisions.Result)
                                    {
                                        this.DrawRevision(revision);
                                    }

                                    this.DrawConnections();

                                    this.Loader.Visibility = Visibility.Collapsed;

                                    this.DrawMinimap();
                                },
                            ui);
                        branches.Start();
                    }
                }
            }
        }

        /// <summary>
        /// Clears the tree.
        /// </summary>
        private void ClearTree()
        {
            this.RevisionsAdded.Clear();
            this.BranchesStack.Children.Clear();
            this.Minimap.Visibility = Visibility.Collapsed;
            this.PathPainter = new PathPainter();
            foreach (Path findChild in this.ScrollContent.FindChildren<Path>().ToList())
            {
                this.ScrollContent.Children.Remove(findChild);
            }

            foreach (Shape findChild in this.ScrollContent.FindChildren<Shape>().ToList())
            {
                this.ScrollContent.Children.Remove(findChild);
            }
        }

        /// <summary>
        /// Gets the branches.
        /// </summary>
        /// <param name="dir">The dir.</param>
        /// <returns>Gets the IEnumerable of branches by given directories.</returns>
        private IEnumerable<Branch> GetBranches(SvnDirectories dir)
        {
            SvnFacade svn = new SvnFacade();
            List<Branch> branches = svn.GetBranchesWithRevisions(dir.SvnDir);
            return branches;
        }

        /// <summary>
        /// Draws the revision.
        /// </summary>
        /// <param name="revision">The revision.</param>
        private void DrawRevision(Revision revision)
        {
            Grid grid = this.BranchesStack.FindChildren<Grid>().Where(x => x.Tag == revision.Branch).FirstOrDefault();
            Grid barContainer = grid.FindChildren<Grid>().Where(x => x.Name == "barContainer").FirstOrDefault();

            RevisionBlock revisionBlock = new RevisionBlock(revision);
            revisionBlock.Margin = new Thickness((revision.Index * revisionBlock.Width) + (revision.Index * 8), 0, 0, 0);
            revisionBlock.HorizontalAlignment = HorizontalAlignment.Left;
            barContainer.Children.Add(revisionBlock);
            this.TreeScroll.UpdateLayout();

            Point transform = revisionBlock.TransformToAncestor(Application.Current.MainWindow).Transform(new Point(0, 0));
            revisionBlock.Location = transform;
            this.RevisionsAdded.Add(revisionBlock);
        }

        /// <summary>
        /// Draws the connections.
        /// </summary>
        private void DrawConnections()
        {
            ConnectionPainter cp = new ConnectionPainter();

            foreach (RevisionBlock block in this.RevisionsAdded)
            {
                //// range of revision items that are our starting point
                foreach (Revision mergeFromRevision in block.Revision.MergeFromRevisions)
                {
                    Revision revision = mergeFromRevision;
                    //// found revision range blocks that have already been added to the UI.
                    List<RevisionBlock> revisionBlocks = this.RevisionsAdded.Where(revisionBlock => revisionBlock.Revision == revision).ToList();

                    foreach (RevisionBlock blockFrom in revisionBlocks)
                    {
                        //// we don't want connections on the same level within one branch.
                        if (blockFrom.Location.Y == block.Location.Y)
                        {
                            continue;
                        }

                        //// this.ScrollContent.Children.Add(PathPainter.GetPath(blockFrom, block));
                        this.ScrollContent.Children.Add(cp.DrawConnection(blockFrom, block));
                        this.TreeScroll.UpdateLayout();
                        block.AddAdditionalToolTipData();

                        this.ConnectionsAdded.Add(new RevisionConnection { FromRevision = blockFrom, ToRevision = block });
                    }
                }
            }
        }

        /// <summary>
        /// Draws the minimap.
        /// </summary>
        private void DrawMinimap()
        {
            this.Minimap.Visibility = Visibility.Visible;
            Visual visualFromUi = this.ScrollContent.GetVisualFromUi(100);
            Minimap.Setup(visualFromUi, this.ScrollContent.ActualWidth, this.ScrollContent.ActualHeight, this.TreeScroll);
        }

        /// <summary>
        /// Windows the size changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.SizeChangedEventArgs"/> instance containing the event data.</param>
        private void WindowSizeChanged(object sender, SizeChangedEventArgs e)
        {
            Minimap.Calibrate();
        }
    }
}
